package cn.wenhaha.app.data.service.imp;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.JarClassLoader;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.setting.dialect.Props;
import cn.wenhaha.app.data.bean.dto.SyncPluginInfoDTO;
import cn.wenhaha.app.data.service.DataPluginService;
import cn.wenhaha.app.data.service.SyncPluginService;
import cn.wenhaha.common.bean.PluginInfo;
import cn.wenhaha.common.bean.entity.Switch;
import cn.wenhaha.common.config.Global;
import cn.wenhaha.common.config.GlobalApp;
import cn.wenhaha.common.db.bean.SysDataSource;
import cn.wenhaha.common.db.bean.SysSyncPlugin;
import cn.wenhaha.common.db.bean.SysUser;
import cn.wenhaha.common.exception.ApiMSGException;
import cn.wenhaha.common.utils.PluginUtils;
import cn.wenhaha.common.utils.PluginsClassLoaderUtil;
import cn.wenhaha.common.utils.ValidatorUtil;
import cn.wenhaha.common.utils.file.FileUtils;
import cn.wenhaha.datasource.IDataSourcePlugin;
import cn.wenhaha.sync.core.DataSyncCoreContext;
import org.pf4j.DependencyResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;


/**
 * 同步核心插件
 * --------
 *
 * @author ：wyndem
 * @Date ：Created in 2022-08-29 20:06
 */
@Service
@Order(2)
public class SyncPluginServiceManagement implements SyncPluginService, ApplicationRunner {

    @Resource
    private ValidatorUtil validatorUtil;

    @Resource
    private SysSyncPlugin syncPlugin;


    @Resource
    private PluginUtils pluginUtils;



    private static final Logger LOG = LoggerFactory.getLogger(SyncPluginServiceManagement.class);


    @Override
    public String addPlugin(MultipartFile file) {
        File libFile = null;
        String id = null;
        try {

            libFile = validateFile(file);
            SyncPluginInfoDTO pluginInfoDTO = pluginValidation(libFile);

            SysSyncPlugin syncPluginJar = syncPlugin.findById(pluginInfoDTO.getCode());
            if (syncPluginJar != null) {
                throw ApiMSGException.build(-1010, StrUtil.format("当前插件已安装，或code冲突,冲突插件名为：{} 版本： {}", syncPluginJar.getName(), syncPluginJar.getVersion()));
            }
            id = installPlugin(libFile.getPath(), pluginInfoDTO.getCode());
            String fileName = pluginInfoDTO.getName() + "_" + pluginInfoDTO.getVersion() + "_" + pluginInfoDTO.getAuthor() + "_" + System.currentTimeMillis() + ".jar";
            String path = PluginUtils.getSyncPluginPath() + File.separator + fileName;

            syncPluginJar = new SysSyncPlugin();
            syncPluginJar.put(BeanUtil.beanToMap(pluginInfoDTO));
            syncPluginJar.setId(id);
            syncPluginJar.setPath(path);
            syncPluginJar.setStatus(Switch.OFF.getCode().toString());
            syncPluginJar.setFileName(fileName);
            syncPluginJar.setCreateTime(DateUtil.now());
            syncPluginJar.setCreateBy(StpUtil.getSession().getModel("info", SysUser.class).getUserName());
            syncPluginJar.save();

            FileUtil.move(libFile, new File(path), true);
            return syncPluginJar.getId();
        }catch (Exception e) {
            e.printStackTrace();
            if (e instanceof ApiMSGException) {
                throw (ApiMSGException) e;
            }
            throw e;
        } finally {
            if (StrUtil.isNotEmpty(id)) {
                pluginUtils.delete(id);
            }
            FileUtil.del(libFile);
        }

    }


    private File validateFile(MultipartFile file){
        try {
            InputStream inputStream = file.getInputStream();
            checkJar(inputStream);
            // 查找插件信息
            String name = FileUtil.mainName(file.getOriginalFilename()) + "_" + RandomUtil.randomString(5) + "." + FileUtil.extName(file.getOriginalFilename());
            String path =  PluginUtils.getSyncPluginPath() + File.separator + name;
            File  libFile = FileUtil.touch(new File(path));
            file.transferTo(libFile);
            IoUtil.close(inputStream);
            return libFile;
        } catch (IOException e) {
            e.printStackTrace();
            throw ApiMSGException.build(-1, e.getMessage());
        }
    }

    private SyncPluginInfoDTO pluginValidation(File file){

        PluginInfo info = Optional.ofNullable(pluginUtils.info(file.getPath()))
                .orElseThrow(() -> ApiMSGException.build(1009));

        Map<String, Object> attr = info.getAttr();
        SyncPluginInfoDTO pluginInfoDTO = new SyncPluginInfoDTO();
        BeanUtil.copyProperties(info, pluginInfoDTO, true);
        pluginInfoDTO.setCode(info.getId());
        pluginInfoDTO.setSupport(MapUtil.getStr(attr, "Plugin-Dependencies"));

        validatorUtil.validate(pluginInfoDTO);

        return  pluginInfoDTO;
    }

    private String installPlugin(String path,String id){
        try {
           return pluginUtils.loadAndStart(path);
        } catch (Exception e) {
            pluginUtils.unload(id);
            throw e;
        }
    }

    @Override
    public String update(MultipartFile file) {
        File libFile = null;
        String id = null;
        try {

            libFile = validateFile(file);
            SyncPluginInfoDTO pluginInfoDTO = pluginValidation(libFile);

            SysSyncPlugin plugin = Optional.ofNullable(syncPlugin.findByCode(pluginInfoDTO.getCode()))
                    .orElseThrow(() -> ApiMSGException.build(7004));
            if (StrUtil.equals(Switch.ON.getCode().toString(), plugin.getStatus())) {
                throw ApiMSGException.build(7005);
            }

            id = installPlugin(libFile.getPath(), pluginInfoDTO.getCode());

            String fileName = pluginInfoDTO.getName() + "_" + pluginInfoDTO.getVersion() + "_" + pluginInfoDTO.getAuthor() + "_" + System.currentTimeMillis() + ".jar";
            String path = PluginUtils.getSyncPluginPath() + File.separator + fileName;
            String oldPath = plugin.getPath();
            plugin.setPath(path);
            plugin.setFileName(file.getOriginalFilename());
            plugin._setOrPut(BeanUtil.beanToMap(pluginInfoDTO));
            plugin.setUpdateTime(DateUtil.now());
            SysUser info = StpUtil.getSession().getModel("info", SysUser.class);
            plugin.setUpdateBy(info.getUserName());
            plugin.update();

            FileUtil.move(libFile, new File(path), true);
            FileUtil.del(oldPath);

            return plugin.getId();
        } finally {
            if (StrUtil.isNotEmpty(id)) {
                pluginUtils.delete(id);
            }
            FileUtil.del(libFile);
        }
    }


    @Override
    public synchronized void enable(SysSyncPlugin syncPlugin) {
        LOG.info("正在加载【{}】数据源插件", syncPlugin.getName());

        try {
            pluginUtils.loadAndStart(syncPlugin.getPath());
        } catch (Exception e) {
            pluginUtils.unload(syncPlugin.getId());
            throw e;
        }

        DataSyncCoreContext obj = Optional.ofNullable(pluginUtils.getObject(DataSyncCoreContext.class, syncPlugin.getId()))
                .orElseThrow(() -> ApiMSGException.build(7006));
        // 触发上下文事件， 添加数据源
        List<String> dataSources = StrUtil.split(syncPlugin.getSupport(), ',');
        dataSources.forEach(d -> {

            try {
                IDataSourcePlugin pluginId = pluginUtils.getObject(IDataSourcePlugin.class, d);
                obj.addDataSource(d, pluginId.getUserContext());
                LOG.info("{}成功添加数据源{}", syncPlugin.getName(), d);
            } catch (Exception e) {
                pluginUtils.unload(syncPlugin.getId());
                throw e;
            }
        });

        syncPlugin.setStatus(Switch.ON.getCode().toString());
        syncPlugin.update();
    }


    @Override
    public synchronized void disabled(SysSyncPlugin syncPlugin) {
        String id = syncPlugin.getId();
        if (!pluginUtils.containPluginId(id)) {
            return;
        }
        LOG.info("正在关闭【{}】数据源插件", syncPlugin.getName());
        pluginUtils.unload(syncPlugin.getId());
        syncPlugin.setStatus(Switch.OFF.getCode().toString());
        syncPlugin.update();

    }

    @Override
    public void remove(String  id) {

        SysSyncPlugin plugin = Optional.ofNullable(syncPlugin.findById(id))
                .orElseThrow(() -> ApiMSGException.build(7009));
        LOG.info("正在删除【{}】数据源插件", plugin.getName());
        pluginUtils.delete(id);
        plugin.delete();

    }


    private void checkJar(InputStream inputStream) throws IOException {
        if (!FileUtils.isJar(inputStream)) {
            throw ApiMSGException.build(1008);
        }
    }

    @Override
    public DataSyncCoreContext getPluginId(String id) {
        if (StrUtil.isEmpty(id)) {
            throw ApiMSGException.build(7010);
        }

        DataSyncCoreContext plugin = pluginUtils.getObject(DataSyncCoreContext.class, id);
        if (plugin == null) {
            ApiMSGException exception = ApiMSGException.build(7009);
            exception.setMessage(exception.getMessage() +":" + id);
            throw exception;
        }

        return plugin;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        if (!GlobalApp.isLoadConfigDb()) {
            LOG.debug("数据暂末配置，不加载同步插件");
            return;
        }
        // 获取已启用的插件原
        List<SysSyncPlugin> sysDataSources = syncPlugin.enableList();
        if (sysDataSources != null && sysDataSources.size() > 0) {
            LOG.info("正在加载同步插件");
            sysDataSources.forEach(this::enable);
        }
    }

}
